/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// RTP Sources containing generic QuickTime stream data, as defined in
//     <http://developer.apple.com/quicktime/icefloe/dispatch026.html>
// Implementation

#include "include/QuickTimeGenericRTPSource.hh"

///// QTGenericBufferedPacket and QTGenericBufferedPacketFactory /////

// A subclass of BufferedPacket, used to separate out
// individual frames (when PCK == 2)

class QTGenericBufferedPacket : public BufferedPacket {
public:
    QTGenericBufferedPacket(QuickTimeGenericRTPSource &ourSource);

    virtual ~QTGenericBufferedPacket();

private: // redefined virtual functions
    virtual unsigned nextEnclosedFrameSize(unsigned char *&framePtr,
                                           unsigned dataSize);

private:
    QuickTimeGenericRTPSource &fOurSource;
};

class QTGenericBufferedPacketFactory : public BufferedPacketFactory {
private: // redefined virtual functions
    virtual BufferedPacket *createNewPacket(MultiFramedRTPSource *ourSource);
};


////////// QuickTimeGenericRTPSource //////////

QuickTimeGenericRTPSource *
QuickTimeGenericRTPSource::createNew(UsageEnvironment &env,
                                     Groupsock *RTPgs,
                                     unsigned char rtpPayloadFormat,
                                     unsigned rtpTimestampFrequency,
                                     char const *mimeTypeString) {
    return new QuickTimeGenericRTPSource(env, RTPgs, rtpPayloadFormat,
                                         rtpTimestampFrequency,
                                         mimeTypeString);
}

QuickTimeGenericRTPSource
::QuickTimeGenericRTPSource(UsageEnvironment &env, Groupsock *RTPgs,
                            unsigned char rtpPayloadFormat,
                            unsigned rtpTimestampFrequency,
                            char const *mimeTypeString)
        : MultiFramedRTPSource(env, RTPgs,
                               rtpPayloadFormat, rtpTimestampFrequency,
                               new QTGenericBufferedPacketFactory),
          fMIMEtypeString(strDup(mimeTypeString)) {
    qtState.PCK = 0;
    qtState.timescale = 0;
    qtState.sdAtom = NULL;
    qtState.sdAtomSize = qtState.width = qtState.height = 0;
}

QuickTimeGenericRTPSource::~QuickTimeGenericRTPSource() {
    delete[] qtState.sdAtom;
    delete[] (char *) fMIMEtypeString;
}

Boolean QuickTimeGenericRTPSource
::processSpecialHeader(BufferedPacket *packet,
                       unsigned &resultSpecialHeaderSize) {
    unsigned char *headerStart = packet->data();
    unsigned packetSize = packet->dataSize();

    // The "QuickTime Header" must be at least 4 bytes in size:
    // Extract the known fields from the first 4 bytes:
    unsigned expectedHeaderSize = 4;
    if (packetSize < expectedHeaderSize) return False;

    unsigned char VER = (headerStart[0] & 0xF0) >> 4;
    if (VER > 1) return False; // unknown header version
    qtState.PCK = (headerStart[0] & 0x0C) >> 2;
#ifdef DEBUG
    Boolean S = (headerStart[0]&0x02) != 0;
#endif
    Boolean Q = (headerStart[0] & 0x01) != 0;

    Boolean L = (headerStart[1] & 0x80) != 0;

#ifdef DEBUG
    Boolean D = (headerStart[2]&0x80) != 0;
    unsigned short payloadId = ((headerStart[2]&0x7F)<<8)|headerStart[3];
#endif
    headerStart += 4;

#ifdef DEBUG
    fprintf(stderr, "PCK: %d, S: %d, Q: %d, L: %d, D: %d, payloadId: %d\n", qtState.PCK, S, Q, L, D, payloadId);
#endif

    if (Q) { // A "QuickTime Payload Description" follows
        expectedHeaderSize += 4;
        if (packetSize < expectedHeaderSize) return False;

#ifdef DEBUG
        Boolean K = (headerStart[0]&0x80) != 0;
        Boolean F = (headerStart[0]&0x40) != 0;
        Boolean A = (headerStart[0]&0x20) != 0;
        Boolean Z = (headerStart[0]&0x10) != 0;
#endif
        unsigned payloadDescriptionLength = (headerStart[2] << 8) | headerStart[3];
        headerStart += 4;

#ifdef DEBUG
        fprintf(stderr, "\tK: %d, F: %d, A: %d, Z: %d, payloadDescriptionLength: %d\n", K, F, A, Z, payloadDescriptionLength);
#endif
        // Make sure "payloadDescriptionLength" is valid
        if (payloadDescriptionLength < 12) return False;
        expectedHeaderSize += (payloadDescriptionLength - 4);
        unsigned nonPaddedSize = expectedHeaderSize;
        expectedHeaderSize += 3;
        expectedHeaderSize -= expectedHeaderSize % 4; // adds padding
        if (packetSize < expectedHeaderSize) return False;
        unsigned char padding = expectedHeaderSize - nonPaddedSize;

#ifdef DEBUG
        unsigned mediaType = (headerStart[0]<<24)|(headerStart[1]<<16)
          |(headerStart[2]<<8)|headerStart[3];
#endif
        qtState.timescale = (headerStart[4] << 24) | (headerStart[5] << 16)
                            | (headerStart[6] << 8) | headerStart[7];
        headerStart += 8;

        payloadDescriptionLength -= 12;
#ifdef DEBUG
        fprintf(stderr, "\tmediaType: '%c%c%c%c', timescale: %d, %d bytes of TLVs left\n", mediaType>>24, (mediaType&0xFF0000)>>16, (mediaType&0xFF00)>>8, mediaType&0xFF, qtState.timescale, payloadDescriptionLength);
#endif

        while (payloadDescriptionLength > 3) {
            unsigned short tlvLength = (headerStart[0] << 8) | headerStart[1];
            unsigned short tlvType = (headerStart[2] << 8) | headerStart[3];
            payloadDescriptionLength -= 4;
            if (tlvLength > payloadDescriptionLength) return False; // bad TLV
            headerStart += 4;
#ifdef DEBUG
            fprintf(stderr, "\t\tTLV '%c%c', length %d, leaving %d remaining bytes\n", tlvType>>8, tlvType&0xFF, tlvLength, payloadDescriptionLength - tlvLength);
            for (int i = 0; i < tlvLength; ++i) fprintf(stderr, "%02x:", headerStart[i]); fprintf(stderr, "\n");
#endif

            // Check for 'TLV's that we can use for our 'qtState'
            switch (tlvType) {
                case ('s' << 8 | 'd'): { // session description atom
                    // Sanity check: the first 4 bytes of this must equal "tlvLength":
                    unsigned atomLength = (headerStart[0] << 24) | (headerStart[1] << 16)
                                          | (headerStart[2] << 8) | (headerStart[3]);
                    if (atomLength != (unsigned) tlvLength) break;

                    delete[] qtState.sdAtom;
                    qtState.sdAtom = new char[tlvLength];
                    memmove(qtState.sdAtom, headerStart, tlvLength);
                    qtState.sdAtomSize = tlvLength;
                    break;
                }
                case ('t' << 8 | 'w'): { // track width
                    qtState.width = (headerStart[0] << 8) | headerStart[1];
                    break;
                }
                case ('t' << 8 | 'h'): { // track height
                    qtState.height = (headerStart[0] << 8) | headerStart[1];
                    break;
                }
            }

            payloadDescriptionLength -= tlvLength;
            headerStart += tlvLength;
        }
        if (payloadDescriptionLength > 0) return False; // malformed TLV data
        headerStart += padding;
    }

    if (L) { // Sample-Specific info follows
        expectedHeaderSize += 4;
        if (packetSize < expectedHeaderSize) return False;

        unsigned ssInfoLength = (headerStart[2] << 8) | headerStart[3];
        headerStart += 4;

#ifdef DEBUG
        fprintf(stderr, "\tssInfoLength: %d\n", ssInfoLength);
#endif
        // Make sure "ssInfoLength" is valid
        if (ssInfoLength < 4) return False;
        expectedHeaderSize += (ssInfoLength - 4);
        unsigned nonPaddedSize = expectedHeaderSize;
        expectedHeaderSize += 3;
        expectedHeaderSize -= expectedHeaderSize % 4; // adds padding
        if (packetSize < expectedHeaderSize) return False;
        unsigned char padding = expectedHeaderSize - nonPaddedSize;

        ssInfoLength -= 4;
        while (ssInfoLength > 3) {
            unsigned short tlvLength = (headerStart[0] << 8) | headerStart[1];
#ifdef DEBUG
            unsigned short tlvType = (headerStart[2]<<8)|headerStart[3];
#endif
            ssInfoLength -= 4;
            if (tlvLength > ssInfoLength) return False; // bad TLV
#ifdef DEBUG
            fprintf(stderr, "\t\tTLV '%c%c', length %d, leaving %d remaining bytes\n", tlvType>>8, tlvType&0xFF, tlvLength, ssInfoLength - tlvLength);
            for (int i = 0; i < tlvLength; ++i) fprintf(stderr, "%02x:", headerStart[4+i]); fprintf(stderr, "\n");
#endif
            ssInfoLength -= tlvLength;
            headerStart += 4 + tlvLength;
        }
        if (ssInfoLength > 0) return False; // malformed TLV data
        headerStart += padding;
    }

    fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame;
    // whether the *previous* packet ended a frame
    fCurrentPacketCompletesFrame = packet->rtpMarkerBit();

    resultSpecialHeaderSize = expectedHeaderSize;
#ifdef DEBUG
    fprintf(stderr, "Result special header size: %d\n", resultSpecialHeaderSize);
#endif
    return True;
}

char const *QuickTimeGenericRTPSource::MIMEtype() const {
    if (fMIMEtypeString == NULL) return MultiFramedRTPSource::MIMEtype();

    return fMIMEtypeString;
}


////////// QTGenericBufferedPacket and QTGenericBufferedPacketFactory impl

QTGenericBufferedPacket
::QTGenericBufferedPacket(QuickTimeGenericRTPSource &ourSource)
        : fOurSource(ourSource) {
}

QTGenericBufferedPacket::~QTGenericBufferedPacket() {
}

unsigned QTGenericBufferedPacket::
nextEnclosedFrameSize(unsigned char *&framePtr, unsigned dataSize) {
    // We use the entire packet for a frame, unless "PCK" == 2
    if (fOurSource.qtState.PCK != 2) return dataSize;

    if (dataSize < 8) return 0; // sanity check

    unsigned short sampleLength = (framePtr[2] << 8) | framePtr[3];
    // later, extract and use the "timestamp" field #####
    framePtr += 8;
    dataSize -= 8;

    return sampleLength < dataSize ? sampleLength : dataSize;
}

BufferedPacket *QTGenericBufferedPacketFactory
::createNewPacket(MultiFramedRTPSource *ourSource) {
    return new QTGenericBufferedPacket((QuickTimeGenericRTPSource &) (*ourSource));
}
